﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;
using ComputerArchitecture.Core.Logging;
using ComputerArchitecture.Numbers;
using ComputerArchitecture.Units.Interfaces;

namespace ComputerArchitecture.Units.Complement
{
    public class ComplementMultiplyUnit : LoggingObject, IMultiplyUnit
    {
        #region Fields

        private readonly IAdderUnit _adderUnit;
        private readonly INegateUnit _negateUnit;

        #endregion

        #region Constructors

        public ComplementMultiplyUnit(ILoggingService loggingService, IAdderUnit adderUnit, INegateUnit negateUnit)
            : base(loggingService)
        {
            ArgumentValidator.NullValidate(adderUnit);
            _adderUnit = adderUnit;
            ArgumentValidator.NullValidate(negateUnit);
            _negateUnit = negateUnit;
        }

        #endregion

        #region Methods

        public ISignedBitNumber Multiply(ISignedBitNumber first, ISignedBitNumber second)
        {
            ArgumentValidator.NullValidate(first);
            ArgumentValidator.NullValidate(second);
            // add bit before and after numbers
            int size = first.Size + second.Size + 2;
            this.LoggingService.LogInfo("");
            this.LoggingService.LogInfo(string.Format("Size of operands (a, s, p): {0}", size));
            // set up A
            this.LoggingService.LogInfo("Get A operand");
            ISignedBitNumber a = new ComplementBitNumber(size);
            first.CopyTo(a, 1, 0, first.Size);
            a[0].SetAs(first[0]);
            this.LoggingService.LogInfo(string.Format("A: {0:d4}\n", a));
            
            // set up S
            this.LoggingService.LogInfo("Get S operand (negate first operand in complement code)");
            ISignedBitNumber s = new ComplementBitNumber(size);
            bool hasError = false;
            NegateSpecialCase specialCase;
            ISignedBitNumber negativeFirst = _negateUnit.Negate(first, out specialCase, out hasError);
            this.LoggingService.LogInfo("Negate first result: {0:d4}", negativeFirst);
            negativeFirst.CopyTo(s, 1, 0, negativeFirst.Size);
            if (hasError && specialCase == NegateSpecialCase.MaxNegativeValue)
            {
                s[0].Reset();
            }
            else
            {
                s[0].SetAs(negativeFirst[0]);
            }
            this.LoggingService.LogInfo(string.Format("S: {0:d4}\n", s));
            
            // set up P
            this.LoggingService.LogInfo("Get P operand (use second operand)");
            ISignedBitNumber p = new ComplementBitNumber(size);
            second.CopyTo(p, first.Size + 1, 0, second.Size);
            this.LoggingService.LogInfo(string.Format("P: {0:d4}\n", p));

            // main cycle
            int preLastIdx = p.Size - 2, lastIdx = p.Size - 1;
            for (int i = 0; i < second.Size; ++i)
            {
                bool overflow = false;
                ISignedBitNumber result = null;
                // p[preLastIdx] != p[lastIdx] - some operations
                // else - nothing
                this.LoggingService.LogInfo("Iteration {0}", i + 1);
                this.LoggingService.LogInfo("Last p-bits {0}{1}", p[preLastIdx], p[lastIdx]);
                if (p[preLastIdx] != p[lastIdx])
                {
                    // if p (....01) => result = p + a;
                    // ignore overflow
                    if (!p[preLastIdx].IsSet && p[lastIdx].IsSet)
                    {
                        this.LoggingService.LogInfo("Add P and A");
                        result = _adderUnit.Add(p, a, out overflow);
                        this.LoggingService.LogInfo("Result (p + a): {0:d4}", result);
                    }
                    else
                    {
                        this.LoggingService.LogInfo("Add P and S");
                        result = _adderUnit.Add(p, s, out overflow);
                        this.LoggingService.LogInfo("Result (p + s): {0:d4}", result);
                    }
                }
                else
                {
                    result = (ISignedBitNumber) p.Clone();
                    this.LoggingService.LogInfo("Do nothing. P is not changed");
                }
                if (ReferenceEquals(result, null))
                {
                    throw new ArgumentNullException("invalid");
                }
                Bit carryBit;
                result.ArithmeticShiftRight(1, out carryBit);
                p = result;
                this.LoggingService.LogInfo("Shift right result of iteration and assign P. New P value: {0:d4}\n", p);
            }

            // clear first and last bits
            ISignedBitNumber multiplyResult = new ComplementBitNumber(first.Size + second.Size);
            p.CopyTo(multiplyResult, 0, 1, multiplyResult.Size);
            return multiplyResult;
        }

        #endregion
    }
}
